/*
  author Sylvain Bertrand <digital.ragnarok@gmail.com>
  Protected by GNU Affero GPL v3 with some exceptions.
  See README at root of alga tree.
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/i2c.h>

#include <alga/alga.h>
#include <alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/dp.h>
#include <alga/edid.h>
#include <alga/amd/atombios/atb.h>
#include <alga/amd/atombios/dce.h>
#include <alga/amd/dce4/dce4.h>
#include <alga/amd/dce4/dce4_dev.h>

#include "dce4.h"
#include "hpd.h"
#include "sink.h"
#include "i2c.h"
#include "dpcd.h"

static int paths_parse(struct dce4 *dce)
{
	struct atb_dp_path *ps;
	size_t ps_n;
	int r;

	dce->dps_used = 0;
	dce->hpds_used = 0;
	r = atb_dp_paths(dce->ddev.atb, &ps, &ps_n);
	if (r == 0 && ps_n ==0)
		dev_warn(dce->ddev.dev, "dce4: no displayport path found\n");
	else if (r == 0 && ps_n !=0) {
		unsigned i;
		for (i = 0; i < ps_n; ++i) {
			struct dp *dp;
			dce->dps_used |= BIT(ps[i].i);

			dp = &dce->dps[ps[i].i];
			dp->atb_dfp = ps[i].dfp;
			dp->atb_aux_i2c_id = ps[i].aux_i2c_id;
			dp->edp = ps[i].edp;
			dp->hpd = ps[i].hpd;
			if (dp->hpd != 0xff)
				dce->hpds_used |= BIT(dp->hpd);

			if (ps[i].uniphy_link_hbr2)
				dp->link_rate_max = GHZ_5_4;
			else
				dp->link_rate_max = GHZ_2_7;
		}
		kfree(ps);
	}
	return r;
}

static void dp_dump(struct dce4 *dce, unsigned i)
{
	struct dp *dp;
	const char *s;

	dp = &dce->dps[i];
	if (dp->edp)
		s = "embedded displayport connector";
	else
		s = "displayport connector";
	dev_info(dce->ddev.dev, "\t%s DFP%u atb_aux_i2c_id=0x%02x", s,
					dp->atb_dfp, dp->atb_aux_i2c_id);
		
	switch (dp->link_rate_max) {
	case GHZ_1_62:
		s = "1.62 GHz";
		break;
	case GHZ_2_7:
		s = "2.7 GHz";
		break;
	case GHZ_5_4:
		s = "5.4 GHz";
		break;
	}
	dev_info(dce->ddev.dev, "\tmax link rate %s\n", s);

	if (dp->hpd != 0xff)
		dev_info(dce->ddev.dev, "\tHPD %u is used\n",dp->hpd);
	else
		dev_info(dce->ddev.dev, "\tno HPD found\n");
}

static void dps_dump(struct dce4 *dce)
{
	unsigned i;

	for (i = 0; i < dce->ddev.crtcs_n; ++i) {
		if ((dce->dps_used & BIT(i)) == 0) {
			dev_info(dce->ddev.dev, "dce4: dp%u not used\n", i);
			continue;
		}
		dev_info(dce->ddev.dev, "dce4: dp%u used\n", i);
		dp_dump(dce, i);
	}
}
	
static void dce_dump(struct dce4 *dce)
{
	dps_dump(dce);
}

static int trans_links_init(struct dce4 *dce)
{
	unsigned i;

	for (i = 0; i < dce->ddev.crtcs_n; ++i) {
		int r;

		if ((dce->dps_used & BIT(i)) == 0)
			continue;		

		r = atb_trans_link_init(dce->ddev.atb, i, dce->dps[i].edp);
		if (r != 0) {
			dev_err(dce->ddev.dev, "dce4: failed to init uniphy "	
							"link for dp%u\n", i);
			return r;
		}
	}
	return 0;
}

static int dps_sense(struct dce4 *dce)
{
	unsigned i;

	//DEBUG
	printk(KERN_INFO "COLDPLUG HPDS SENSING:\n");
	for (i = 0; i < dce->ddev.crtcs_n; ++i) {
		struct dp *dp;
		bool sense;
		int r;

		if ((dce->dps_used & BIT(i)) == 0)
			continue;

		dp = &dce->dps[i];

		/* no hpd, presume edp and always connected */
		if (dp->hpd == 0xff) {
			//DEBUG
			printk(KERN_INFO "\tdp%u has no HPD\n", i);
			r = dp_i2c_adapter_init(dce, i);
			if (r != 0)
				return r;
		
			dp->connected = true;
			atb_dp_state(dce->ddev.atb, dp->atb_dfp, true);	
			continue;
		}

		sense = dce->ddev.hpd_sense(dce->ddev.dev, dp->hpd);
		dce->ddev.hpd_polarity_rearm(dce->ddev.dev, dp->hpd, sense);
		//DEBUG
		if (sense){
			printk(KERN_INFO "\tHPD%u CONNECTED\n",dp->hpd);
		} else {
			printk(KERN_INFO "\tHPD%u DISCONNECTED\n",dp->hpd);
		}
		
		r = dp_i2c_adapter_init(dce, i);	
		if (r != 0)
			return r;
		dp->connected = sense;
		atb_dp_state(dce->ddev.atb, dp->atb_dfp, sense);
	}
	return 0;
}

static int dp_dpm_off(struct dce4 *dce, unsigned i)
{
	int r;

	r = atb_trans_link_output_off(dce->ddev.atb, i);
	if (r != 0) {
		r = -DCE4_ERR;
		goto err;
	}

	r = atb_enc_video(dce->ddev.atb, i, false);
	if (r != 0) {
		r = -DCE4_ERR;
		goto err;
	}

	if (dce->dps[i].edp) {/* shutdown sink power only for edp */
		r = atb_trans_link_pwr(dce->ddev.atb, i, false);
		if (r != 0) {
			r = -DCE4_ERR;
			goto err;
		}
	}

	r = atb_crtc_blank(dce->ddev.atb, i, true);
	if (r != 0) {
		r = -DCE4_ERR;
		goto err;
	}

	r = atb_crtc_mem_req(dce->ddev.atb, i, false);
	if (r != 0) {
		r = -DCE4_ERR;
		goto err;
	}

	r = atb_crtc(dce->ddev.atb, i, false);
	if (r != 0)
		r = -DCE4_ERR;
err:
	return r;
}

static int dps_used_pwr_on(struct dce4 *dce)
{
	int r;
	unsigned i;

	r = 0;
	for (i = 0; i < dce->ddev.crtcs_n; ++i) {
		if ((dce->dps_used & BIT(i)) == 0)
			continue;
		r = atb_trans_link_pwr(dce->ddev.atb, i , true);
		if (r != 0)
			goto err;
	}
	msleep(300);/* follow timing specs from AMD code */
err:
	return r;
}

static int dps_used_dpm_off(struct dce4 *dce)
{
	int r;
	unsigned i;
	
	r = 0;
	for (i = 0; i < dce->ddev.crtcs_n; ++i) {
		if ((dce->dps_used & BIT(i)) == 0)
			continue;

		r  = dp_dpm_off(dce, i);
		if (r != 0)
			break;
	}
	return r;
}

static int dp_off(struct dce4 *dce, unsigned i)
{
	int r;

	r = atb_trans_link_output_off(dce->ddev.atb, i);
	if (r != 0)
		goto err;

	r = atb_trans_link_pwr(dce->ddev.atb, i, false);
	if (r != 0)
		goto err;

	r = atb_trans_link_off(dce->ddev.atb, i);
	if (r != 0)
		goto err;

	r = atb_crtc_blank(dce->ddev.atb, i, true);
	if (r != 0)
		goto err;

	r = atb_crtc_mem_req(dce->ddev.atb, i, false);
	if (r != 0)
		goto err;

	r = atb_crtc(dce->ddev.atb, i, false);
err:
	return r;
}

static int dps_unused_off(struct dce4 *dce)
{
	int r;
	unsigned i;

	for (i = 0; i < dce->ddev.crtcs_n; ++i) {
		if ((dce->dps_used & BIT(i)) != 0)
			continue;

		r = dp_off(dce, i);
		if (r != 0)
			break;
	}
	return r;
}

static int dps_connected_dpcd_info(struct dce4 *dce)
{
	int r;
	unsigned i;

	for (i = 0; i < dce->ddev.crtcs_n; ++i) {
		if ((dce->dps_used & BIT(i)) == 0)
			continue;

		if (!dce->dps[i].connected)
			continue;

		r = dpcd_info(dce, i);
		if (r != 0)
			break;
	}
	return r;
}

void dp_link_specs_init(struct dce4 *dce, unsigned i)
{
	if (dce->dps[i].dpcd_info[1] >= dce->dps[i].link_rate_max)
		dce->dps[i].link_rate = dce->dps[i].link_rate_max;
	else
		dce->dps[i].link_rate =
				(enum link_rate)(dce->dps[i].dpcd_info[1]);

	switch (dce->dps[i].dpcd_info[2] & DPCD_MAX_LANE_COUNT_MASK) {
	case 0x1:
		dce->dps[i].lanes_n = 1;
		break;
	case 0x2:
		dce->dps[i].lanes_n = 2;
		break;
	case 0x4:
		dce->dps[i].lanes_n = 4;
		break;
	}
}

static void dps_link_specs_init(struct dce4 *dce)
{
	unsigned i;

	for (i = 0; i < dce->ddev.crtcs_n; ++i) {
		if ((dce->dps_used & BIT(i)) == 0)
			continue;

		if (!dce->dps[i].connected)
			continue;

		dp_link_specs_init(dce, i);
	}
}

int dce4_sink_timings(struct dce4 *dce, unsigned i, struct alga_timing **ts)
{
	int r;

	lock(dce);

	if (dce->state == DCE4_INVALID) {
		r = -DCE4_ERR;
		goto unlock;
	}

	if ((dce->dps_used & BIT(i)) == 0) {
		r = -DCE4_ERR;
		goto unlock;
	}

	if (!dce->dps[i].connected) {
		r = -DCE4_ERR;
		goto unlock;
	}

	r = sink_timings(dce, i, ts);

unlock:
	unlock(dce);
	return r;
}
EXPORT_SYMBOL_GPL(dce4_sink_timings);

int dce4_sink_pixel_fmts(struct dce4 *dce, unsigned i,
						enum alga_pixel_fmt **fmts)
{
	int r;

	lock(dce);

	if (dce->state == DCE4_INVALID) {
		r = -DCE4_ERR;
		goto unlock;
	}

	if ((dce->dps_used & BIT(i)) == 0) {
		r = -DCE4_ERR;
		goto unlock;
	}

	if (!dce->dps[i].connected) {
		r = -DCE4_ERR;
		goto unlock;
	}

	r = pixel_fmts(dce, i, fmts);

unlock:
	unlock(dce);
	return r;
}
EXPORT_SYMBOL_GPL(dce4_sink_pixel_fmts);

int dce4_sink_mode_set(struct dce4 *dce, unsigned i, u64 fb,
			enum alga_pixel_fmt pixel_fmt, struct alga_timing *t)
{
	int r;

	if (dce->state == DCE4_INVALID) {
		r = -DCE4_ERR;
		goto unlock;
	}

	if ((dce->dps_used & BIT(i)) == 0) {
		r = -DCE4_ERR;
		goto unlock;
	}

	if (!dce->dps[i].connected) {
		r = -DCE4_ERR;
		goto unlock;
	}

	r = sink_mode_set(dce, i, fb, pixel_fmt, t);
unlock:
	unlock(dce);
	return r;
}
EXPORT_SYMBOL_GPL(dce4_sink_mode_set);

static void dps_edid_free(struct dce4 *dce)
{
	unsigned i;

	for (i = 0; i < dce->ddev.crtcs_n; ++i)
		if (dce->dps[i].edid != NULL) {
			kfree(dce->dps[i].edid);
			dce->dps[i].edid = NULL;
		}
}

static void edid(struct dce4 *dce, unsigned i)
{
	int r;

	r = alga_i2c_edid_fetch(dce->ddev.dev, &dce->dps[i].i2c_adapter,
							&dce->dps[i].edid);
	if (r != 0)
		dev_warn(dce->ddev.dev, "dce4: unable to fetch edid for dp%u\n",
									i);
}

static void dps_connected_edid(struct dce4 *dce)
{
	unsigned i;

	for (i = 0; i < dce->ddev.crtcs_n; ++i) {
		if ((dce->dps_used & BIT(i)) == 0)
			continue;

		if (!dce->dps[i].connected)
			continue;

		/* it's not fatal to fail to get the edid */
		edid(dce, i);
	}
}

int dce4_init(struct dce4 *dce, struct dce4_dev *ddev)
{
	int r;

	dce->state = DCE4_INVALID;
	dce->ddev = *ddev;

	mutex_init(&dce->mutex);

	r = paths_parse(dce);
	if (r != 0)
		goto err;

	dce_dump(dce);

	r = trans_links_init(dce);	
	if (r != 0)
		goto err;

	r = atb_crtc_dcpll(ddev->atb);
	if (r != 0)
		goto err;

	hpd_init(dce);

	r = dps_used_pwr_on(dce);
	if (r != 0)
		goto err;
	
	r = dps_used_dpm_off(dce);
	if (r != 0)
		goto err;

	r = dps_unused_off(dce);
	if (r != 0)
		goto err;

	r = dps_sense(dce);
	if (r != 0)
		goto err;

	r = dps_connected_dpcd_info(dce);
	if (r != 0)
		goto err;

	dps_link_specs_init(dce);
	dps_connected_edid(dce);

	dce->state = DCE4_RUNNING;
err:
	return r;
}
EXPORT_SYMBOL_GPL(dce4_init);

void dce4_cleanup(struct dce4 *dce)
{
	lock(dce);
	if (dce->state == DCE4_INVALID)
		goto unlock;

	dps_edid_free(dce);
	dp_i2c_cleanup(dce);
	dce->state = DCE4_INVALID;

unlock:
	unlock(dce);
}	
EXPORT_SYMBOL_GPL(dce4_cleanup);

struct dce4 *dce4_alloc(void)
{
	return kzalloc(sizeof(struct dce4), GFP_KERNEL);
}
EXPORT_SYMBOL_GPL(dce4_alloc);

static int __init init(void)
{
	return 0;
}

static void __exit cleanup(void)
{
}

module_init(init);
module_exit(cleanup);

MODULE_AUTHOR("Sylvain Bertrand <digital.ragnarok@gmail.com>");
MODULE_DESCRIPTION("AMD DCE4");
MODULE_LICENSE("GPL");
